// **********************************************************************
// 
// <copyright>
// 
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
// 
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
// 
// </copyright>
// **********************************************************************
// 
// $Source:
// /cvs/distapps/openmap/src/openmap/com/bbn/openmap/tools/roads/Road.java,v
// $
// $RCSfile: Road.java,v $
// $Revision: 1.5 $
// $Date: 2005/12/09 21:09:11 $
// $Author: dietrick $
// 
// **********************************************************************

package com.bbn.openmap.tools.roads;

import java.awt.Point;
import java.io.Serializable;
import java.util.logging.Logger;

import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.proj.GreatCircle;
import com.bbn.openmap.proj.coords.LatLonPoint;

public class Road implements RoadObject, Serializable {

    transient Logger logger = Logger.getLogger(this.getClass().getName());

    /**
     * The points along the road. The first and last points are always
     * Intersections.
     */
    private Waypoint[] points;

    /**
     * The lines between the points.
     */
    private RoadLine[] lines;

    /**
     * The class of this road. A Road's class implies what kind of road it is.
     * For now this governs its visual appearance of color and width.
     */
    private RoadClass roadClass;

    private boolean isRoute = false;

    private boolean blocked = false;

    /**
     * The id of this road.
     */
    private int id;

    /**
     * The name of this road.
     */
    private String name;

    /**
     * True if this road has been modified (points added or removed).
     */
    private boolean modified = false;

    /**
     * The RoadLayer we belong to so we can invoke its services.
     */
    private transient RoadLayer roadLayer;

    /**
     * Selection flag for this road.
     */
    private boolean selected = false;

    /**
     * Blink flag for this road.
     */
    private boolean blinkState = false;

    /**
     * Create a road between two Intersections. The detailed segments between
     * the intersections may be filled in later.
     * 
     * @param id a unique, integer identifier of this road.
     * @param name a name for this road.
     * @param from the intersection from which this road leaves.
     * @param to the intersection to which this road goes.
     * @param roadClass the class of this road.
     * @param roadLayer the RoadLayer we belong to.
     */
    public Road(int id, String name, Intersection from, Intersection to, RoadClass roadClass,
            RoadLayer roadLayer) {
        this.id = id;
        this.name = name;
        this.roadLayer = roadLayer;
        points = new Waypoint[2];
        setIntersections(from, to);
        this.roadClass = roadClass;
        createLines();
        modified = false;
    }

    public double getLengthInKilometers() {
        double kilometers = 0.0;
        LatLonPoint prevPoint = points[0].getLocation();
        // logger.warning ("" + this + " pt 0 " + points[0] + " pt 1 "
        // + points[1] + " getSecondInter " + getSecondIntersection
        // ());
        for (int i = 1; i < points.length; i++) {
            LatLonPoint thisPoint = points[i].getLocation();
            kilometers += GreatCircle.sphericalDistance(prevPoint.getY(), prevPoint.getX(), thisPoint.getY(), thisPoint.getX());
            prevPoint = thisPoint;
        }
        return kilometers;
    }

    public double getTraverseHours() {
        if (isBlocked())
            return Float.MAX_VALUE;
        return getLengthInKilometers() / getRoadClass().getConvoySpeed();
    }

    public LatLonPoint getLocationAtKilometer(double kilometers) {
        LatLonPoint prevPoint = points[0].getLocation();
        double prevLat = prevPoint.getY();
        double prevLon = prevPoint.getX();
        for (int i = 1; i < points.length; i++) {
            LatLonPoint thisPoint = points[i].getLocation();
            double thisLat = thisPoint.getY();
            double thisLon = thisPoint.getX();
            double thisLength = GreatCircle.sphericalDistance(prevLat, prevLon, thisLat, thisLon);
            if (thisLength >= kilometers) {
                double fraction = kilometers / thisLength;
                double deltaLat = thisLat - prevLat;
                double deltaLon = thisLon - prevLon;
                if (deltaLon < -180f)
                    deltaLon += 360f;
                else if (deltaLon > 180f)
                    deltaLon -= 360f;
                return new LatLonPoint.Double(prevLat + fraction * deltaLat, prevLon + fraction
                        * deltaLon);
            }
            kilometers -= thisLength;
            prevPoint = thisPoint;
            prevLat = thisLat;
            prevLon = thisLon;
        }
        return prevPoint;
    }

    private void createLines() {
        lines = new RoadLine[points.length - 1];
        for (int i = 0; i < lines.length; i++) {
            lines[i] = new RoadLine(this, i);
        }
        blinkLines();
    }

    private void blinkLines() {
        for (int i = 0; i < lines.length; i++)
            lines[i].blink(blinkState);
    }

    /**
     * Set the state of the modified flag. Setting the modified flag to false
     * also sets the modified flag of all the points to false as well.
     * 
     * @param newValue the new setting.
     */
    public void setModified(boolean newValue) {
        modified = newValue;
        if (newValue == false) {
            for (int i = 0; i < points.length; i++)
                points[i].setModified(false);
        }
    }

    /**
     * Get the state of the modified flag.
     * 
     * @return true if the road or its points have been modified.
     */
    public boolean getModified() {
        if (modified)
            return true;
        for (int i = 0; i < points.length; i++)
            if (points[i].getModified())
                return true;
        return false;
    }

    public void block() {
        blocked = true;
        updateLines();
    }

    public void unblock() {
        blocked = false;
        updateLines();
    }

    public boolean isBlocked() {
        return blocked;
    }

    public void blink(boolean newState) {
        blinkState = newState;
        if (lines != null)
            blinkLines();
        blinkPoints();
    }

    /**
     * Accessor for the ID property.
     * 
     * @return the road ID.
     */
    public int getID() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String newName) {
        name = newName;
    }

    public RoadClass getRoadClass() {
        return roadClass;
    }

    public void setRoadClass(RoadClass newClass) {
        roadClass = newClass;
        setModified(true);
        updateLines();
    }

    public String getRoadClassName() {
        return roadClass.getName().toString();
    }

    public void isRoute(boolean yes) {
        isRoute = yes;
        updateLines();
    }

    public boolean isRoute() {
        return isRoute;
    }

    public RoadLayer getRoadLayer() {
        return roadLayer;
    }

    public void setIntersections(Intersection from, Intersection to) {
        if (from == null) {
            logger.warning("from is null.");
            Thread.dumpStack();
        }
        if (to == null) {
            logger.warning("to is null.");
            Thread.dumpStack();
        }

        points[0] = from;
        points[points.length - 1] = to;
        checkPoints();
        createLines();
        setModified(true);
    }

    public void setRoadPoints(RoadPoint[] innerPoints) {
        Waypoint[] oldPoints = points;
        points = new Waypoint[2 + innerPoints.length];
        points[0] = oldPoints[0];
        System.arraycopy(innerPoints, 0, points, 1, innerPoints.length);
        points[points.length - 1] = oldPoints[oldPoints.length - 1];

        if (points[points.length - 1] == null) {
            logger.warning("to is null.");
            Thread.dumpStack();
        }

        checkPoints();

        createLines();
        // blinkPoints();
        setModified(true);
    }

    public void checkPoints() {
        for (int i = 0; i < points.length; i++) {
            if (points[i] == null) {
                logger.warning("found null point at " + i);
                Thread.dumpStack();
            }
        }
    }

    public RoadPoint[] getRoadPoints() {
        RoadPoint[] innerPoints = new RoadPoint[points.length - 2];
        System.arraycopy(points, 1, innerPoints, 0, innerPoints.length);
        return innerPoints;
    }

    public void insertRoadPointAt(RoadPoint wp, int ix) {
        Waypoint[] oldPoints = points;
        points = new Waypoint[1 + oldPoints.length];
        System.arraycopy(oldPoints, 0, points, 0, ix);
        points[ix] = wp;
        if (wp == null) {
            logger.warning("wp is null.");
            Thread.dumpStack();
        }
        checkPoints();
        System.arraycopy(oldPoints, ix, points, ix + 1, oldPoints.length - ix);
        createLines();
        // blinkPoints();
        setModified(true);
    }

    public void deleteRoadPoint(RoadPoint rp) {
        for (int ix = 1; ix < points.length - 1; ix++) {
            if (points[ix] == rp) {
                Waypoint[] oldPoints = points;
                points = new Waypoint[oldPoints.length - 1];
                System.arraycopy(oldPoints, 0, points, 0, ix);
                System.arraycopy(oldPoints, ix + 1, points, ix, oldPoints.length - ix - 1);
                createLines();
                setModified(true);
                return;
            }
        }
        checkPoints();

    }

    public Intersection getFirstIntersection() {
        return (Intersection) points[0];
    }

    public Intersection getSecondIntersection() {
        return (Intersection) points[points.length - 1];
    }

    public Intersection getOtherIntersection(Intersection intersection) {
        if (intersection == points[0])
            return (Intersection) points[points.length - 1];
        return (Intersection) points[0];
    }

    public void changeIntersection(Intersection oldIntersection, Intersection newIntersection) {
        if (oldIntersection == points[0]) {
            setIntersections(newIntersection, getSecondIntersection());
        } else if (oldIntersection == points[points.length - 1]) {
            setIntersections(getFirstIntersection(), newIntersection);
        }
        checkPoints();

    }

    public Waypoint getWaypoint(int ix) {
        return points[ix];
    }

    public Waypoint[] getPoints() {
        return points;
    }

    public RoadPoint[] getPointsBefore(RoadPoint wp) {
        for (int i = 1; i < points.length - 1; i++) {
            if (points[i] == wp) {
                RoadPoint[] answer = new RoadPoint[i - 1];
                System.arraycopy(points, 1, answer, 0, answer.length);
                return answer;
            }
        }
        return new RoadPoint[0];
    }

    public RoadPoint[] getPointsAfter(RoadPoint wp) {
        for (int i = 1; i < points.length - 1; i++) {
            if (points[i] == wp) {
                RoadPoint[] answer = new RoadPoint[points.length - i - 2];
                System.arraycopy(points, i + 1, answer, 0, answer.length);
                return answer;
            }
        }
        RoadPoint[] answer = new RoadPoint[points.length - 2];
        System.arraycopy(points, 1, answer, 0, answer.length);
        return answer;
    }

    private void blinkPoints() {
        for (int i = 1; i < points.length - 1; i++) {
            points[i].blink(blinkState);
        }
    }

    /**
     * Mark this Road as needing a new visual representation.
     */
    public synchronized void updateLines() {
        for (int i = 0; i < lines.length; i++)
            lines[i].update();
    }

    public void moveTo(Point loc) {
    }

    public synchronized void render(OMGraphicList gl, boolean projectionIsNew) {
        if (roadLayer.isEditing()) {
            for (int i = 1; i < points.length - 1; i++) {
                points[i].render(gl, projectionIsNew);
            }
        }
        for (int i = 0; i < lines.length; i++)
            lines[i].render(gl, projectionIsNew);
    }

    public String toString() {
        return name + " from " + getFirstIntersection() + " to " + getSecondIntersection() + " "
                + points.length + " points.";
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}